package com.forum.auth.util;

import com.forum.common.constant.CommonConstants;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT工具类
 */
@Slf4j
@Component
public class JwtUtil {
    /**
     * 密钥
     */
    @Value("${jwt.secret}")
    private String secret;

    /**
     * 令牌有效期（秒）
     */
    @Value("${jwt.token-validity-in-seconds}")
    private long tokenValidityInSeconds;

    /**
     * 记住我令牌有效期（秒）
     */
    @Value("${jwt.token-validity-in-seconds-for-remember-me}")
    private long tokenValidityInSecondsForRememberMe;

    /**
     * 创建密钥
     * 注意：根据JWT规范(RFC 7518)，HMAC-SHA算法使用的密钥必须至少为256位(32字节)
     * 确保application.yml中配置的jwt.secret足够长
     *
     * @return 密钥
     */
    private SecretKey getSecretKey() {
        return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    private Claims getClaimsFromToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSecretKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * 生成令牌
     *
     * @param userId     用户ID
     * @param username   用户名
     * @param rememberMe 记住我
     * @return 令牌
     */
    public String generateToken(Long userId, String username, boolean rememberMe) {
        long expiration = rememberMe ? tokenValidityInSecondsForRememberMe : tokenValidityInSeconds;
        Date expirationDate = new Date(System.currentTimeMillis() + expiration * 1000);

        Map<String, Object> claims = new HashMap<>(3);
        claims.put("userId", userId);
        claims.put("username", username);
        claims.put("created", new Date());

        return generateToken(claims, expirationDate);
    }

    /**
     * 生成令牌
     *
     * @param claims        数据声明
     * @param expirationDate 过期时间
     * @return 令牌
     */
    private String generateToken(Map<String, Object> claims, Date expirationDate) {
        Date now = new Date();

        JwtBuilder builder = Jwts.builder()
                .setId(CommonConstants.JWT_ID)
                .setIssuedAt(now)
                .setClaims(claims)
                .setExpiration(expirationDate)
                .signWith(getSecretKey(), SignatureAlgorithm.HS256);

        return builder.compact();
    }

    /**
     * 验证令牌
     *
     * @param token 令牌
     * @return 是否有效
     */
    public boolean validateToken(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            Date expiration = claims.getExpiration();
            Date now = new Date();
            return expiration.after(now);
        } catch (Exception e) {
            log.error("验证令牌失败", e);
            return false;
        }
    }

    /**
     * 从令牌中获取用户ID
     *
     * @param token 令牌
     * @return 用户ID
     */
    public Long getUserIdFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return Long.valueOf(claims.get("userId").toString());
    }

    /**
     * 从令牌中获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public String getUsernameFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.get("username").toString();
    }

    /**
     * 获取令牌过期时间
     *
     * @param token 令牌
     * @return 过期时间
     */
    public Date getExpirationDateFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();
    }

    /**
     * 计算令牌剩余有效期（秒）
     *
     * @param token 令牌
     * @return 剩余有效期（秒）
     */
    public long getRemainingTimeFromToken(String token) {
        Date expiration = getExpirationDateFromToken(token);
        Date now = new Date();
        return (expiration.getTime() - now.getTime()) / 1000;
    }
} 